{ "cells": [ { "cell_type": "markdown", "id": "ea63cd0b-9873-4afd-815c-35a056bc0e37", "metadata": {}, "source": [ "# 注文遅延データ\n", "\n", "より現実的なバックテスト結果を得るためには、レイテンシを考慮することが重要です。したがって、フィードデータと注文データの両方をタイムスタンプ付きで収集し、注文遅延を測定する必要があります。最良のアプローチは、自分自身の注文遅延を収集することです。ライブ取引に基づいて注文遅延を収集するか、定期的に埋められない価格で注文を提出し、記録のためにそれらをキャンセルすることで注文遅延を収集できます。ただし、それらにアクセスできない場合や目標を設定したい場合は、人工的に注文遅延を生成する必要があります。このレイテンシは、フィードレイテンシ、取引量、イベントの数などの要因に基づいてモデル化できます。このガイドでは、調整のための乗数とオフセットを使用して、フィードレイテンシから注文遅延を生成する簡単な方法を示します。" ] }, { "cell_type": "markdown", "id": "ce73ae54-2e57-489e-919e-d1f9271f4461", "metadata": {}, "source": [ "まず、フィードデータを読み込みます。" ] }, { "cell_type": "code", "execution_count": null, "id": "ab04f05e-259b-4a68-b701-1df3f2c19a82", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([(3758096386, 1580515202342000000, 1580515202497052000, 9364.51, 1.197, 0, 0, 0.),\n", " (3758096386, 1580515202342000000, 1580515202497346000, 9365.67, 0.02 , 0, 0, 0.),\n", " (3758096386, 1580515202342000000, 1580515202497352000, 9365.86, 0.01 , 0, 0, 0.),\n", " ...,\n", " (3489660929, 1580601599836000000, 1580601599962961000, 9351.47, 3.914, 0, 0, 0.),\n", " (3489660929, 1580601599836000000, 1580601599963461000, 9397.78, 0.1 , 0, 0, 0.),\n", " (3489660929, 1580601599848000000, 1580601599973647000, 9348.14, 3.98 , 0, 0, 0.)],\n", " dtype=[('ev', '\n", "shape: (27_532_602, 8)
evexch_tslocal_tspxqtyorder_idivalfval
i64i64i64f64f64u64i64f64
3758096386158051520234200000015805152024970520009364.511.197000.0
3758096386158051520234200000015805152024973460009365.670.02000.0
3758096386158051520234200000015805152024973520009365.860.01000.0
3758096386158051520234200000015805152024973570009366.360.002000.0
3758096386158051520234200000015805152024973630009366.360.003000.0
3489660929158060159981200000015806015999444040009397.790.0000.0
3489660929158060159982600000015806015999521760009354.84.07000.0
3489660929158060159983600000015806015999629610009351.473.914000.0
3489660929158060159983600000015806015999634610009397.780.1000.0
3489660929158060159984800000015806015999736470009348.143.98000.0
" ], "text/plain": [ "shape: (27_532_602, 8)\n", "┌────────────┬─────────────────────┬────────────────────┬─────────┬───────┬──────────┬──────┬──────┐\n", "│ ev ┆ exch_ts ┆ local_ts ┆ px ┆ qty ┆ order_id ┆ ival ┆ fval │\n", "│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\n", "│ i64 ┆ i64 ┆ i64 ┆ f64 ┆ f64 ┆ u64 ┆ i64 ┆ f64 │\n", "╞════════════╪═════════════════════╪════════════════════╪═════════╪═══════╪══════════╪══════╪══════╡\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249705200 ┆ 9364.51 ┆ 1.197 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249734600 ┆ 9365.67 ┆ 0.02 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249735200 ┆ 9365.86 ┆ 0.01 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249735700 ┆ 9366.36 ┆ 0.002 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249736300 ┆ 9366.36 ┆ 0.003 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\n", "│ 3489660929 ┆ 1580601599812000000 ┆ 158060159994440400 ┆ 9397.79 ┆ 0.0 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3489660929 ┆ 1580601599826000000 ┆ 158060159995217600 ┆ 9354.8 ┆ 4.07 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3489660929 ┆ 1580601599836000000 ┆ 158060159996296100 ┆ 9351.47 ┆ 3.914 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3489660929 ┆ 1580601599836000000 ┆ 158060159996346100 ┆ 9397.78 ┆ 0.1 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "│ 3489660929 ┆ 1580601599848000000 ┆ 158060159997364700 ┆ 9348.14 ┆ 3.98 ┆ 0 ┆ 0 ┆ 0.0 │\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\n", "└────────────┴─────────────────────┴────────────────────┴─────────┴───────┴──────────┴──────┴──────┘" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import polars as pl\n", "\n", "df = pl.DataFrame(data)\n", "df" ] }, { "cell_type": "markdown", "id": "0a2f5db2-b6ab-420d-b5f9-0ca8e123607b", "metadata": {}, "source": [ "有効な取引所タイムスタンプと有効なローカルタイムスタンプの両方を持つイベントのみを選択して、フィードレイテンシを取得します。" ] }, { "cell_type": "code", "execution_count": null, "id": "349335b0-50a4-4e3a-a721-c55388d71b01", "metadata": {}, "outputs": [], "source": [ "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "df = df.filter(\n", " (pl.col(\"ev\") & EXCH_EVENT == EXCH_EVENT)\n", " & (pl.col(\"ev\") & LOCAL_EVENT == LOCAL_EVENT)\n", ")" ] }, { "cell_type": "markdown", "id": "e641eb14-5370-4cc3-bf52-e940d94745a1", "metadata": {}, "source": [ "約1秒間隔にリサンプリングして行数を減らします。" ] }, { "cell_type": "code", "execution_count": null, "id": "577ad180-42ad-46f8-9723-1b2c9b14d9c2", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "shape: (86_394, 2)
exch_tslocal_ts
i64i64
15805152028430000001580515202979365000
15805152035510000001580515203943566000
15805152037890000001580515204875639000
15805152041270000001580515205962135000
15805152047380000001580515206983780000
15806015958690000001580601595997115000
15806015968650000001580601596994060000
15806015978640000001580601597987786000
15806015988700000001580601598997068000
15806015998480000001580601599973647000
" ], "text/plain": [ "shape: (86_394, 2)\n", "┌─────────────────────┬─────────────────────┐\n", "│ exch_ts ┆ local_ts │\n", "│ --- ┆ --- │\n", "│ i64 ┆ i64 │\n", "╞═════════════════════╪═════════════════════╡\n", "│ 1580515202843000000 ┆ 1580515202979365000 │\n", "│ 1580515203551000000 ┆ 1580515203943566000 │\n", "│ 1580515203789000000 ┆ 1580515204875639000 │\n", "│ 1580515204127000000 ┆ 1580515205962135000 │\n", "│ 1580515204738000000 ┆ 1580515206983780000 │\n", "│ … ┆ … │\n", "│ 1580601595869000000 ┆ 1580601595997115000 │\n", "│ 1580601596865000000 ┆ 1580601596994060000 │\n", "│ 1580601597864000000 ┆ 1580601597987786000 │\n", "│ 1580601598870000000 ┆ 1580601598997068000 │\n", "│ 1580601599848000000 ┆ 1580601599973647000 │\n", "└─────────────────────┴─────────────────────┘" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = (\n", " df.with_columns(pl.col(\"local_ts\").alias(\"ts\"))\n", " .group_by_dynamic(\"ts\", every=\"1000000000i\")\n", " .agg(pl.col(\"exch_ts\").last(), pl.col(\"local_ts\").last())\n", " .drop(\"ts\")\n", ")\n", "\n", "df" ] }, { "cell_type": "markdown", "id": "7834be83-788e-48ae-92e9-0f01bda90cd5", "metadata": {}, "source": [ "構造化されたNumPy配列に戻します。" ] }, { "cell_type": "code", "execution_count": 5, "id": "d69e7709-d947-4e62-b5a4-c8f1e623de2a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([(1580515202843000000, 1580515202979365000),\n", " (1580515203551000000, 1580515203943566000),\n", " (1580515203789000000, 1580515204875639000), ...,\n", " (1580601597864000000, 1580601597987786000),\n", " (1580601598870000000, 1580601598997068000),\n", " (1580601599848000000, 1580601599973647000)],\n", " dtype=[('exch_ts', '\n", "shape: (86_394, 4)
req_tsexch_tsresp_ts_padding
i64i64i64i64
1580515202979365000158051520352482500015805152039339200000
1580515203943566000158051520551383000015805152066915280000
1580515204875639000158051520922219500015805152124821120000
1580515205962135000158051521330267500015805152188080800000
1580515206983780000158051521596690000015805152227042400000
1580601595997115000158060159650957500015806015968939200000
1580601596994060000158060159751030000015806015978974800000
1580601597987786000158060159848293000015806015988542880000
1580601598997068000158060159950534000015806015998865440000
1580601599973647000158060160047623500015806016008531760000
" ], "text/plain": [ "shape: (86_394, 4)\n", "┌─────────────────────┬─────────────────────┬─────────────────────┬──────────┐\n", "│ req_ts ┆ exch_ts ┆ resp_ts ┆ _padding │\n", "│ --- ┆ --- ┆ --- ┆ --- │\n", "│ i64 ┆ i64 ┆ i64 ┆ i64 │\n", "╞═════════════════════╪═════════════════════╪═════════════════════╪══════════╡\n", "│ 1580515202979365000 ┆ 1580515203524825000 ┆ 1580515203933920000 ┆ 0 │\n", "│ 1580515203943566000 ┆ 1580515205513830000 ┆ 1580515206691528000 ┆ 0 │\n", "│ 1580515204875639000 ┆ 1580515209222195000 ┆ 1580515212482112000 ┆ 0 │\n", "│ 1580515205962135000 ┆ 1580515213302675000 ┆ 1580515218808080000 ┆ 0 │\n", "│ 1580515206983780000 ┆ 1580515215966900000 ┆ 1580515222704240000 ┆ 0 │\n", "│ … ┆ … ┆ … ┆ … │\n", "│ 1580601595997115000 ┆ 1580601596509575000 ┆ 1580601596893920000 ┆ 0 │\n", "│ 1580601596994060000 ┆ 1580601597510300000 ┆ 1580601597897480000 ┆ 0 │\n", "│ 1580601597987786000 ┆ 1580601598482930000 ┆ 1580601598854288000 ┆ 0 │\n", "│ 1580601598997068000 ┆ 1580601599505340000 ┆ 1580601599886544000 ┆ 0 │\n", "│ 1580601599973647000 ┆ 1580601600476235000 ┆ 1580601600853176000 ┆ 0 │\n", "└─────────────────────┴─────────────────────┴─────────────────────┴──────────┘" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_order_latency = pl.DataFrame(order_latency)\n", "df_order_latency" ] }, { "cell_type": "markdown", "id": "0c0eb2a2-64dd-4d4a-93f6-0c52a4bdc142", "metadata": {}, "source": [ "レイテンシに無効な負の値がないか確認します。" ] }, { "cell_type": "code", "execution_count": null, "id": "542eace5-590e-4d30-a552-0cf0825885d5", "metadata": {}, "outputs": [], "source": [ "order_entry_latency = df_order_latency[\"exch_ts\"] - df_order_latency[\"req_ts\"]\n", "order_resp_latency = df_order_latency[\"resp_ts\"] - df_order_latency[\"exch_ts\"]" ] }, { "cell_type": "code", "execution_count": 9, "id": "a64aad08-888f-48e7-85cc-1719ba3333b6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(order_entry_latency <= 0).sum()" ] }, { "cell_type": "code", "execution_count": 10, "id": "7e770e94-4660-4b0b-827b-951a046d9529", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(order_resp_latency <= 0).sum()" ] }, { "cell_type": "markdown", "id": "18944b3a-747c-4c38-96e1-0ab6930e098c", "metadata": {}, "source": [ "ここでは、速度を上げるために`njit`を使用してプロセス全体をメソッドにラップします。" ] }, { "cell_type": "code", "execution_count": null, "id": "15b080d5-9e5f-4124-a83f-813bb2ff9414", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import polars as pl\n", "from numba import njit\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "from hftbacktest import EXCH_EVENT, LOCAL_EVENT\n", "\n", "\n", "@njit\n", "def generate_order_latency_nb(\n", " data, order_latency, mul_entry, offset_entry, mul_resp, offset_resp\n", "):\n", " for i in range(len(data)):\n", " exch_ts = data[i].exch_ts\n", " local_ts = data[i].local_ts\n", " feed_latency = local_ts - exch_ts\n", " order_entry_latency = mul_entry * feed_latency + offset_entry\n", " order_resp_latency = mul_resp * feed_latency + offset_resp\n", "\n", " req_ts = local_ts\n", " order_exch_ts = req_ts + order_entry_latency\n", " resp_ts = order_exch_ts + order_resp_latency\n", "\n", " order_latency[i].req_ts = req_ts\n", " order_latency[i].exch_ts = order_exch_ts\n", " order_latency[i].resp_ts = resp_ts\n", "\n", "\n", "def generate_order_latency(\n", " feed_file, output_file=None, mul_entry=1, offset_entry=0, mul_resp=1, offset_resp=0\n", "):\n", " data = np.load(feed_file)[\"data\"]\n", " df = pl.DataFrame(data)\n", "\n", " df = (\n", " df.filter(\n", " (pl.col(\"ev\") & EXCH_EVENT == EXCH_EVENT)\n", " & (pl.col(\"ev\") & LOCAL_EVENT == LOCAL_EVENT)\n", " )\n", " .with_columns(pl.col(\"local_ts\").alias(\"ts\"))\n", " .group_by_dynamic(\"ts\", every=\"1000000000i\")\n", " .agg(pl.col(\"exch_ts\").last(), pl.col(\"local_ts\").last())\n", " .drop(\"ts\")\n", " )\n", "\n", " data = df.to_numpy(structured=True)\n", "\n", " order_latency = np.zeros(\n", " len(data),\n", " dtype=[\n", " (\"req_ts\", \"i8\"),\n", " (\"exch_ts\", \"i8\"),\n", " (\"resp_ts\", \"i8\"),\n", " (\"_padding\", \"i8\"),\n", " ],\n", " )\n", " generate_order_latency_nb(\n", " data, order_latency, mul_entry, offset_entry, mul_resp, offset_resp\n", " )\n", "\n", " if output_file is not None:\n", " np.savez_compressed(output_file, data=order_latency)\n", "\n", " return order_latency" ] }, { "cell_type": "code", "execution_count": null, "id": "09772349-cebb-4698-8c6f-b77cbd19f15b", "metadata": {}, "outputs": [], "source": [ "order_latency = generate_order_latency(\n", " \"btcusdt_20200201.npz\",\n", " output_file=\"feed_latency_20200201.npz\",\n", " mul_entry=4,\n", " mul_resp=3,\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.14" } }, "nbformat": 4, "nbformat_minor": 5 }